/// TWL_AUTOMENU.CPP
/*
 * Steve Donovan, 2003
 * This is GPL'd software, and the usual disclaimers apply.
 * See LICENCE
 */
#include <list>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "twl_automenu.h"
#include "twl_menu.h"

// at end of this file
char *i2a(int i);
int safe_atoi(const char *str);
bool filecmp(const char* f1, const char* f2);
char* strip_matching_dirs(const char* cwd, const char* path, char *res);

// constants for iterate_menu();
const int SEARCH = 1, INDEX = 2, CREATE = 4, DELETE = 8, SAVE = 16, REMOVE = 32;

static int _start_ = 2000;  // quite arbitrary! the system should look after this!

struct FileItem {
	char* filename;  // _full_ path!
	long  file_pos;
	void* data;
};

typedef std::list<FileItem> FileItemList;
typedef FileItemList* PFileItemList;

int AutoMenu::count()
{
  return PFileItemList(m_items)->size();
}

// this odd fiddle is necessary to force a bool return to zero out
// the whole EAX register (definitely a UCW workaround! See ucri.cpp
// for another example)
bool bool4(unsigned long ul)
{ return (bool) ul; }

// this must be called directly from the window's command() override.
int  AutoMenu::handle(int id)
{
	if (id > m_start_id && id <= m_start_id + m_max_items)  
       return select(id - m_start_id);     
    else return false;
}

bool AutoMenu::match(const char* filename)
{
  return bool4(iterate_menu(SEARCH,(void*)filename));
}

bool AutoMenu::select(int idx)
{
  return bool4(iterate_menu(INDEX,(void*)idx));
}

bool AutoMenu::remove(int idx)
{
    iterate_menu(DELETE);
    iterate_menu(REMOVE,(void*)idx);
    iterate_menu(CREATE);
    return bool4(true);
}

void AutoMenu::add_item_to_list(const char *filename, void* data, bool at_end)
{
  FileItem item;
  item.filename = strdup(filename);
  item.file_pos = 0;
  item.data = data;
  if (at_end) PFileItemList(m_items)->push_back(item);
  else PFileItemList(m_items)->push_front(item);
}

void AutoMenu::strip_cwd(bool yesno)    
{ 
  if (yesno != m_strip_cwd) {
    iterate_menu(DELETE);
    m_strip_cwd = yesno;
    iterate_menu(CREATE);
  }
}

// add a filename to the automenu
void  AutoMenu::add_item(const char *filename, void *data)
{
 iterate_menu(DELETE);                 // clear menu
 PFileItemList items = PFileItemList(m_items);
 FileItemList::iterator ili;
 if (iterate_menu(SEARCH,(void*)filename)) ;   // if it's already there, promote it
 else {  // otherwise, have to add it.
	 add_item_to_list(filename,data,false);  // add to begining of list
     if (items->size() > m_max_items)        // too many items!
	   items->pop_back();                    // so drop the end
  }
 iterate_menu(CREATE);                  // recreate menu
}

int AutoMenu::iterate_menu(int op, void* target)
{
	char buff[256], obuff[256];
	PopupMenu popup(m_form,m_popup_idx);
	PFileItemList items = PFileItemList(m_items);
	FileItemList::iterator is,is_begin = items->begin(),is_end = items->end();
	int idx = 1;
	for(is = is_begin; is != is_end; ++is,++idx) {
	  if (op == CREATE) {	
          char* file_caption = is->filename;
          if (m_strip_cwd) file_caption = strip_matching_dirs(m_cwd,file_caption,obuff);
          sprintf(buff,"&%d %s",idx,file_caption);
		  Item item(buff,NULL,cmd_id(idx));		  
		  popup.insert_menu(start_id(),item);  
	  }	  else 
      if (op == DELETE) popup.delete_menu(cmd_id(idx)); else
      if (op == REMOVE && idx == (int)target) {
          items->erase(is);
          return 1;
      } else
	  if (op == SAVE)  {
         sprintf(buff,"%s,%ld,",is->filename,is->file_pos);
	     m_ini->write_string(i2a(idx),buff);
      } else
	  if (op == SEARCH && filecmp((const char*)target,is->filename)) {
		  if (is != is_begin) {        // existing item, _not_ at the front
		    items->push_front(*is);    // promote this item to the front of the list
		    items->erase(is);          // erase the old item.
          }
		  return 1;
      } else
	  if (op == INDEX && idx == (int)target) {	  
         m_curr_file = is->filename;
		 m_curr_pos  = is->file_pos;
		 m_curr_data = is->data;
		 return 1;
      }
    }
	return 0;
}

AutoMenu::AutoMenu(TEventWindow *form, IniBase *ini, int popup_idx, char *section, int max_items)
	  : m_form(form),m_ini(ini),m_popup_idx(popup_idx),m_section(section),m_max_items(max_items),
	  m_files_overflow(false),m_id(0),m_strip_cwd(false)
{
    ini->set_section(section);
    m_id = 0;
	m_start_id = _start_;
	_start_ += m_max_items + 5;
	m_items = new FileItemList();
}

// read the files from an ini source;  call it explicitly after defining your menus.
void  AutoMenu::read()
{
	char buff[256];
	m_ini->set_section(m_section);
	// build up the list of menu items, read from the ini source;
	// we know when we're finished when there's no key corresp to an index
    for(int i = 1; i <= m_max_items; i++) {
       m_ini->read_string(i2a(i),buff,256,"");
       if (*buff == '\0') break;           // no more items!
       m_curr_file = strtok(buff,",");     // always present!
  	   m_curr_pos  = safe_atoi(strtok(NULL,","));     // not always....
  	   add_item_to_list(m_curr_file,NULL,true); // to the end
	}
	iterate_menu(CREATE);  // and create the menu
}

// the corresponding operation which writes the list back to the ini source;
// must be called explicitly when your app closes.
void AutoMenu::write()
{
  m_ini->set_section(m_section);
  iterate_menu(SAVE);
}

// some useful utility functions...i2a() is actually exported by YAWL

char *i2a(int i) 
{
  static char tmpbuff[30];
  return itoa(i,tmpbuff,10);
}  

int safe_atoi(const char *str)
{
	if (! str) return 0;
	else return atoi(str);
}

bool filecmp(const char* f1, const char* f2)
{
	return _stricmp(f1,f2) == 0;   // Win32 *ALERT*
}

char buff[256];

char DIR_SEP = '\\';
char* DOWN_DIR = "..\\";

char* strip_matching_dirs(const char* cwd, const char* path, char *res)
{
  *res = '\0';
  // first non-matching character
  while (*cwd++ == *path++) ;
 // move back onto the character
  path--;
  if (*cwd != '\0') cwd--;
 // cd be a partial match; mosey back to find the '/'
  if (*cwd != '\0' && *cwd != DIR_SEP) {
    while (*cwd != DIR_SEP) {
        cwd--;
        path--;
    }
    path++;  // don't want the '/' in the path
  }
  // count slashes
  int k = 0;
  if (*cwd == DIR_SEP) cwd++;  // past '/'
  while (*cwd) {
    if (*cwd == DIR_SEP) k++;
    cwd++;
  }
  if (k > 0) {
    strcpy(res,DOWN_DIR);
    for (int i = 1; i < k; i++)
      strcat(res,DOWN_DIR);
  }
  strcat(res,path);
  return res;
}
 
